package files

import (
	"mime"
	"os"
	"path/filepath"
	"strings"

	"gitee.com/xuender/reader/pb"
	"github.com/h2non/filetype"
	"github.com/h2non/filetype/types"
)

var codes = []string{
	"abap", "apex", "azcli", "bat", "bicep", "cameligo",
	"clojure", "coffee", "cpp", "cc", "hpp", "csharp", "csp",
	"css", "dart", "dockerfile", "ecl", "elixir", "fsharp",
	"go", "graphql", "handlebars", "hcl", "html", "htm",
	"htma", "xhtml", "jsp", "ini", "toml", "java", "javascript", "js",
	"json", "julia", "kotlin", "less", "lexon", "liquid", "lua",
	"m3", "markdown", "md", "mips", "msdax", "mysql",
	"objective-c", "c", "h", "pascal", "pascaligo", "perl",
	"pgsql", "php", "postiats", "powerquery", "powershell",
	"ps", "pug", "python", "py", "r", "razor", "redis",
	"redshift", "restructuredtext", "ruby", "rust", "sb", "scala",
	"scheme", "scss", "shell", "sh", "solidity", "sophia", "sql",
	"st", "swift", "systemverilog", "tcl", "twig", "typescript", "ts",
	"vb", "xml", "yaml", "svg",
}

// FileTypeByBytes 根据字节获取文件类型.
func FileTypeByBytes(head []byte, name string) (pb.FileType, pb.ArchiveType) {
	kind, err := filetype.Match(head)
	if err != nil {
		return pb.FileType_unknown, pb.ArchiveType_none
	}

	t, at := match(kind)

	if t == pb.FileType_unknown && isText(head) {
		name = strings.ToLower(name)
		for _, c := range codes {
			if strings.HasSuffix(name, c) {
				return pb.FileType_code, pb.ArchiveType_none
			}
		}

		return pb.FileType_text, pb.ArchiveType_none
	}

	if t == pb.FileType_archive || t == pb.FileType_unknown {
		ctype := mime.TypeByExtension(filepath.Ext(name))
		if strings.Contains(ctype, "document") {
			return pb.FileType_document, pb.ArchiveType_none
		}
	}

	return t, at
}

// FileType 文件类型.
func FileType(paths ...string) (pb.FileType, pb.ArchiveType) {
	path := filepath.Join(paths...)

	file, err := os.Open(path)
	if err != nil {
		return pb.FileType_unknown, pb.ArchiveType_none
	}
	defer file.Close()

	head := make([]byte, 261)

	size, err := file.Read(head)
	if err != nil {
		return pb.FileType_unknown, pb.ArchiveType_none
	}

	return FileTypeByBytes(head[:size], path)
}

func match(kind types.Type) (pb.FileType, pb.ArchiveType) {
	switch kind.MIME.Type {
	case "image":
		return pb.FileType_image, pb.ArchiveType_none
	case "video":
		return pb.FileType_video, pb.ArchiveType_none
	case "audio":
		return pb.FileType_audio, pb.ArchiveType_none
	case "application":
		return application(kind.MIME)
	}

	return pb.FileType_unknown, pb.ArchiveType_none
}

func application(mime types.MIME) (pb.FileType, pb.ArchiveType) {
	if mime.Subtype == "pdf" {
		return pb.FileType_pdf, pb.ArchiveType_none
	}

	if mime.Subtype == "zip" {
		return pb.FileType_archive, pb.ArchiveType_zip
	}

	if mime.Subtype == "gzip" {
		return pb.FileType_archive, pb.ArchiveType_gzip
	}

	if strings.HasPrefix(mime.Subtype, "font") {
		return pb.FileType_font, pb.ArchiveType_none
	}

	if mime.Subtype == "wasm" || strings.HasPrefix(mime.Subtype, "vnd.android") {
		return pb.FileType_application, pb.ArchiveType_none
	}

	if mime.Subtype == "msword" || strings.HasPrefix(mime.Subtype, "vnd") {
		return pb.FileType_document, pb.ArchiveType_none
	}

	return pb.FileType_archive, pb.ArchiveType_none
}

func isText(bs []byte) bool {
	var binCode byte = 9

	for _, b := range bs {
		if b < binCode {
			return false
		}
	}

	return true
}
